package com.vois.jack.btmgr.classicbase;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Message;

import com.vois.jack.btmgr.common.DebugMsgStr;
import com.vois.jack.btmgr.common.State;
import com.vois.jack.btmgr.common.StateMachine;
import com.vois.jack.btmgr.util.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BtDevMgr extends StateMachine implements BtHelper.BtHelperListener {
    private Logger logger = Logger.getLogger(BtDevMgr.class);
    private InitState initState;
    private BluetoothOnState bluetoothOnState;
    private BluetoothOffState bluetoothOffState;
    private Map<String, BtDevice> btDeviceMap;
    private BtDevListener listener;
    private BtDevice.BtDeviceListener commonDeviceListener;
    private Context appContext;
    private boolean isInited;
    private boolean isIniting;
    private BroadcastReceiver btBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
                logger.d( "onReceive: ACTION_DISCOVERY_STARTED");
            }else if (intent.getAction().equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                logger.d( "onReceive: ACTION_DISCOVERY_FINISHED");
            }else if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                int curState, preState;
                curState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
                preState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_OFF);
                sendMessage(BtFsmMsg.BT_FSM_STATE_MSG.ordinal(), curState, preState);
                logger.d( "onReceive: ACTION_STATE_CHANGED: cur:" + DebugMsgStr.valueOf(curState) + " prev:" + DebugMsgStr.valueOf(preState));
            }else if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
                logger.d( "onReceive: ACTION_FOUND");
                BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (bluetoothDevice != null) {
                    logger.d( "onReceive: addr:" + bluetoothDevice.getAddress());
                    if (bluetoothDevice.getName() != null) {
                        logger.d( "onReceive: " + bluetoothDevice.getName());
                    }
                }else {
                    logger.d( "onReceive: get null device!!!!!!!!");
                }
            }else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                logger.d( "onReceive: ACTION_BOND_STATE_CHANGED");
            }else if (intent.getAction().equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device != null) {
                    int curState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.STATE_DISCONNECTED);
                    int preState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, BluetoothAdapter.STATE_DISCONNECTED);

                    if (getCurrentState() != null) {
                        logger.d( "onReceive: " + getCurrentState().getName());
                    }
                    logger.d( "onReceive: initied:" + isInited);
                    sendMessage(obtainMessage(BtFsmMsg.BT_FSM_CONNECT_STATE_CHANGED_MSG.ordinal(), curState, preState, device));
                    logger.d( "onReceive: ACTION_CONNECTION_STATE_CHANGED:" + DebugMsgStr.valueOf(curState) + " " + DebugMsgStr.valueOf(preState));
                }else {
                    logger.d( "onReceive: get null device!!!!!!!!!");
                }
            }else if (intent.getAction().equals(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                logger.d( "onReceive: ACTION_VENDOR_SPECIFIC_HEADSET_EVENT");
                if (device != null) {
                    BtDevice btDevice = btDeviceMap.get(device.getAddress());
                    if (btDevice != null) {
                        try {
                            String cmd = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);
                            int cmdType = intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, BluetoothHeadset.AT_CMD_TYPE_TEST);
                            Object[] args = (Object[]) intent.getSerializableExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
                            logger.d("onReceive: cmd:" + cmd + " cmdType:" + cmdType);
                            btDevice.processXEvent(cmd, cmdType, args);
                        }catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }else {
                    logger.d( "onReceive: get null device!!!!!!!!!");
                }
            }else if (intent.getAction().equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (bluetoothDevice != null) {
                    logger.d( "onReceive: ACTION_AUDIO_STATE_CHANGED:" + state);
                    BtDevice btDevice = btDeviceMap.get(bluetoothDevice.getAddress());
                    if (btDevice != null && btDevice.getListener() != null) {
                        Bundle bundle = new Bundle();
                        Message message = Message.obtain();
                        message.what = BtDevCommonMsg.BT_DEV_HEADSET_AUDIO_STATE_CHANGED.getValue();
                        bundle.putInt(BtDevConstant.EXTRA_AUDIO_STATE, state);
                        message.setData(bundle);
                        btDevice.getListener().onMessage(btDevice, message);
                    }
                }else {
                    logger.d( "onReceive: get null device!!!!!!!!");
                }
            }else if (intent.getAction().equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device != null) {
                    BtDevice btDevice = btDeviceMap.get(device.getAddress());
                    int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
                    if (btDevice != null) {
                        btDevice.processHeadsetConnectState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED),
                                intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED));
                    } else {
                        sendMessage(obtainMessage(BtFsmMsg.BT_FSM_CONNECT_STATE_CHANGED_MSG.ordinal(), state, prevState, device));
                    }

                    logger.d( "onReceive: headset ACTION_CONNECTION_STATE_CHANGED:" + DebugMsgStr.valueOf(state));
                    if (getCurrentState() != null) {
                        logger.d( "onReceive: " + getCurrentState().getName());
                    }
                }else {
                    logger.d( "onReceive: get null device..............");
                }
            }else if (intent.getAction().equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device != null) {
                    BtDevice btDevice = btDeviceMap.get(device.getAddress());
                    int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
                    if (btDevice != null) {
                        btDevice.processA2DPConnectState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED),
                                intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED));
                    }
                    logger.d( "onReceive: a2dp ACTION_CONNECTION_STATE_CHANGED:" + DebugMsgStr.valueOf(state));
                }else {
                    logger.d( "onReceive: get null device!!!!!!!!!");
                }
            }else if (intent.getAction().equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
                for (BtDevice btDevice: btDeviceMap.values()) {
                    btDevice.processHeadsetScoState(intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED));
                }

                logger.d( "onReceive: ACTION_SCO_AUDIO_STATE_UPDATED: " + intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED));
            }
        }
    };

    public interface BtDevListener {
        void onBtMgrStarted();
        void onOpenningBtDevice(BtDevice btDevice);
        void onBtMgrStopped();
    }

    class InitState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;

            if (msg.what < 0) {
                logger.e("InitState msg.what:" + msg.what);
                return ret;
            }

            BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
            logger.d( "processMessage: InitState:" + btFsmMsg);
            switch (btFsmMsg) {
                case BT_FSM_STATE_MSG: {
                    int state = msg.arg1;
                    if (state == BluetoothAdapter.STATE_ON) {
                        logger.d( "InitState: transition to bluetooth on");
                        transitionTo(bluetoothOnState);
                    }else if (state == BluetoothAdapter.STATE_OFF) {
                        logger.d( "InitState: transition to bluetooth off");
                        transitionTo(bluetoothOffState);
                    }

                    ret = true;
                }
                break;
            }
            return ret;
        }

        @Override
        public void enter() {
            logger.d( "enter: InitState");
            int state = BtHelper.getInstance().getBluetoothState();
            logger.d( "enter: state:" + DebugMsgStr.valueOf(state));
            if (state == BluetoothAdapter.STATE_ON) {
                logger.d( "enter: transition to bluetooth on");
                transitionTo(bluetoothOnState);
            }else if (state == BluetoothAdapter.STATE_OFF) {
                logger.d( "enter: transition to bluetooth off");
                transitionTo(bluetoothOffState);
            }

            if (listener != null) {
                listener.onBtMgrStarted();
            }

            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
            filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
            filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
            filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
            filter.addAction(BluetoothDevice.ACTION_UUID);
            filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
            filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS);
            appContext.registerReceiver(btBroadcastReceiver, filter);
            isInited = true;
            isIniting = false;
        }

        @Override
        public void exit() {
            logger.d( "exit: InitState");
        }
    }

    class BluetoothOnState extends State {
        @Override
        public void enter() {
            logger.d( "enter: BluetoothOnState");
            sendMessage(BtFsmMsg.BT_FSM_BTDEV_INIT_MSG.ordinal());
        }

        @Override
        public void exit() {
            logger.d( "exit: BluetoothOnState");
            for (BtDevice btDevice: btDeviceMap.values()) {
                btDevice.closeDevice();
            }

            btDeviceMap.clear();
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                logger.e("BluetoothOnState msg.what:" + msg.what);
                return ret;
            }

            BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
            logger.d( "processMessage: BluetoothOnState:" + btFsmMsg);
            switch (btFsmMsg) {
                case BT_FSM_BTDEV_INIT_MSG: {
                    List<BluetoothDevice> headsetDevies = BtHelper.getInstance().getConnectedHeadsetDevices();
                    if (headsetDevies != null) {
                        for (BluetoothDevice device : headsetDevies) {
                            if (!btDeviceMap.containsKey(device.getAddress())) {
                                openBtDevice(device);
                            }
                        }
                    }
                    ret = true;
                }
                break;

                case BT_FSM_STATE_MSG: {
                    int state = msg.arg1;
                    logger.d( "BT_FSM_STATE_MSG: " + DebugMsgStr.valueOf(state));
                    if (state == BluetoothAdapter.STATE_OFF) {
                        logger.d( "processMessage: transition to bluetooth off");
                        transitionTo(bluetoothOffState);
                    }
                    ret = true;
                }
                break;

                case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                    int state = msg.arg1;
                    BluetoothDevice bluetoothDevice = (BluetoothDevice)msg.obj;
                    logger.d( "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state) + " addr:" + bluetoothDevice.getAddress());
                    if (state == BluetoothAdapter.STATE_CONNECTED) {
                        if (!btDeviceMap.containsKey(bluetoothDevice.getAddress())) {
                            openBtDevice(bluetoothDevice);
                        }
                    }
                    ret = true;
                }
                break;

                case BT_FSM_BOUND_STATE_CHANGED_MSG: {

                }
                 break;
            }
            return ret;
        }
    }

    class BluetoothOffState extends State {
        @Override
        public void enter() {
            logger.d( "enter: BluetoothOffState");
            for (BtDevice btDevice: btDeviceMap.values()) {
                btDevice.closeDevice();
            }
            btDeviceMap.clear();
        }

        @Override
        public void exit() {
            logger.d( "exit: BluetoothOffState");
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                logger.e("BluetoothOffState msg.what:" + msg.what);
                return ret;
            }

            BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
            logger.d( "processMessage: BluetoothOffState:" + btFsmMsg);
            switch (btFsmMsg) {
                case BT_FSM_STATE_MSG: {
                    int state = msg.arg1;
                    logger.d( "BluetoothOffState BT_FSM_STATE_MSG: " + DebugMsgStr.valueOf(state));
                    if (state == BluetoothAdapter.STATE_ON) {
                        logger.d( "processMessage: transition to bluetooth on");
                        transitionTo(bluetoothOnState);
                    }
                    ret = true;
                }
                break;

                case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                    deferMessage(msg);
                    ret = true;
                }
                break;
            }
            return ret;
        }
    }

    public BtDevMgr() {
        super("BTDevManager");
        logger.d( "BtDevMgr: ");
        isInited = false;
        isIniting = false;
        initState = new InitState();
        bluetoothOnState = new BluetoothOnState();
        bluetoothOffState = new BluetoothOffState();
        addState(initState);
        addState(bluetoothOnState, initState);
        addState(bluetoothOffState, initState);

        setInitialState(initState);
    }

    @Override
    public void onInited() {
        logger.d( "onInited: " + isInited);
        if (!isInited) {
            start();
        }
    }

    @Override
    protected void onQuitting() {
        logger.d( "onQuitting: ");
        if (listener != null) {
            listener.onBtMgrStopped();
        }

        isInited = false;
    }

    public void init(Context appContext) throws IllegalStateException {
        logger.d( "init: ");
        if (!isIniting) {
            this.appContext = appContext;
            BtHelper.getInstance().init(appContext, this);
            btDeviceMap = new HashMap<>();
            isIniting = true;
        }
    }

    public void deInit() {
        logger.d( "deInit: ");
        if (isIniting || isInited) {
            if (isInited) {
                appContext.unregisterReceiver(btBroadcastReceiver);
                isInited = false;

                for (String mac : btDeviceMap.keySet()) {
                    btDeviceMap.get(mac).closeDevice();
                }
                btDeviceMap.clear();
                quitNow();
            }

            BtHelper.getInstance().deInit();
            isIniting = false;
        }
    }

    public void openBtDevice(BluetoothDevice device) {
        if (device.getName() != null) {
            logger.d("openBtDevice: name:" + device.getName());
        }else{
            logger.d("openBtDevice: name:" + device.getAddress());
        }
        if (!btDeviceMap.containsKey(device.getAddress())) {
            BtDevice btDevice = BtDeviceBuilder.buildBtDevice(device);

            if (listener != null) {
                listener.onOpenningBtDevice(btDevice);
            }

            btDeviceMap.put(device.getAddress(), btDevice);
            btDevice.openDevice();

            if (commonDeviceListener != null) {
                btDevice.setListener(commonDeviceListener);
            }
        }
    }

    public void closeBtDevice(BluetoothDevice device) {
        if (device.getName() != null) {
            logger.d("closeBtDevice: name:" + device.getName());
        }else {
            logger.d("closeBtDevice: name:" + device.getAddress());
        }
        if (btDeviceMap.containsKey(device.getAddress())) {
            BtDevice btDevice = btDeviceMap.get(device.getAddress());

            if (btDevice != null) {
                btDevice.closeDevice();
                btDeviceMap.remove(btDevice.getDevice().getAddress());
            }
        }
    }

    public void closeBtDevice(String addr) {
        logger.d("1 closeBtDevice " + addr);
        if (btDeviceMap.containsKey(addr)) {
            logger.d("2 closeBtDevice " + addr);
            BtDevice btDevice = btDeviceMap.get(addr);

            if (btDevice != null) {
                btDevice.closeDevice();
                btDeviceMap.remove(btDevice.getDevice().getAddress());
            }
        }
    }

    public List<BtDevice> getOpennedBluetoothDevices() {
        if (!btDeviceMap.isEmpty()) {
            List<BtDevice> list = new ArrayList<>(btDeviceMap.values());
            return list;
        }

        return null;
    }

    public BtDevice getBluetoothDevice(String address) {
        if (address != null) {
            if (btDeviceMap.containsKey(address)) {
                return btDeviceMap.get(address);
            }
        }

        return null;
    }

    public void setListener(BtDevListener listener) {
        this.listener = listener;
    }

    public void setCommonDeviceListener(BtDevice.BtDeviceListener commonDeviceListener) {
        this.commonDeviceListener = commonDeviceListener;
    }
}
